package com.aotain.oframework.util.xml;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.DOMReader;
import org.dom4j.io.DOMWriter;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXValidator;
import org.dom4j.io.XMLWriter;
import org.dom4j.util.XMLErrorHandler;
import org.xml.sax.SAXException;

import com.aotain.oframework.exception.XMLException;
import com.aotain.oframework.util.lang.StringUtil;

/**
 * @author Jacob.cw.Zhang@gmail.com
 */
public final class XMLUtils {
	
	private static Log log = LogFactory.getLog(XMLUtils.class);

	/**
	 * 通过XSD（XML Schema）校验XML
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws URISyntaxException
	 */
	public static Element validateXMLByXSD(Document xmlDocument,
			String xsdFileName) throws XMLException, URISyntaxException,
			IOException {
		URI uri = new URI("file:" + xsdFileName);
		return validateXMLByXSD(xmlDocument, uri);

	}

	public static Element validateXMLByXSD(Document xmlDocument, URL url)
			throws XMLException, URISyntaxException, IOException {
		if (url != null) {
			return validateXMLByXSD(xmlDocument, url.toURI());
		} else {
			throw new IOException("XSD File Not Found!");
		}
	}

	public static SAXValidator getInstatance(String xsdUri)
			throws URISyntaxException, ParserConfigurationException,
			SAXException {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		factory.setValidating(true);
		factory.setNamespaceAware(true);
		URI xsdUris = new URI(xsdUri);
		SAXParser parser = factory.newSAXParser();
		parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
		parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", xsdUris.toString());
		SAXValidator validator = new SAXValidator(parser.getXMLReader());
		return validator;
	}

	public static Element validateXMLByXSD(Document xmlDocument, URI xsdUri) throws XMLException {
		Element errors = DocumentHelper.createElement("result");
		XMLErrorHandler errorHandler = new XMLErrorHandler();
		SAXValidator validator;
		try {
			validator = getInstatance(xsdUri.toString());
			validator.setErrorHandler(errorHandler);
			validator.validate(xmlDocument);
			XMLWriter writer = new XMLWriter(OutputFormat.createPrettyPrint());
			if (errorHandler.getErrors().hasContent()) {
				log.info("Validation Failed!");
				writer.write(errorHandler.getErrors());
				errors.add(errorHandler.getErrors());
			} else {
				log.info("Validation Passed!");
			}
			return errors;
		} catch (URISyntaxException e) {
			throw new XMLException("URISyntaxException", "URI Syntax Exception.", e);
		} catch (ParserConfigurationException e) {
			throw new XMLException("ParserConfigurationException", "Parser exception.", e);
		} catch (SAXException e) {
			throw new XMLException("SAXException", "SAX exception.", e);
		} catch (IOException e) {
			throw new XMLException("IOException", "XMLWriter exception.", e);
		}
	}

	public static Element validateXMLByXSD(String xmlFileName,
			String xsdFileName) throws XMLException, URISyntaxException,
			IOException, DocumentException {
		SAXReader xmlReader = new SAXReader();
		Document xmlDocument = xmlReader.read(new File(xmlFileName));
		return validateXMLByXSD(xmlDocument, xsdFileName);
	}

	public static File doc2XmlFile(Document document, String filename) {
		try {
			/* 将document中的内容写入文件*/
			// 默认为UTF-8格式，指定为"GB2312"
			File file = new File(filename);
			OutputFormat format = OutputFormat.createPrettyPrint();
			XMLWriter writer = new XMLWriter(new FileWriter(file), format);
			writer.write(document);
			writer.close();
			return file;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	public static List<String> getValueListOfElement(String xml, String xPath) {
		if (StringUtil.isEmptyString(xml) || StringUtil.isEmptyString(xPath)) {
			return new ArrayList<String>();
		}
		List<String> result = new ArrayList<String>();
		SAXReader reader = new SAXReader();
		try {
			Document document = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			List<Element> children = document.selectNodes(xPath);
			for (Element child : children) {
				result.add(child.getText());
			}
		} catch (UnsupportedEncodingException e) {
			log.debug(e.getMessage());
		} catch (DocumentException e) {
			log.debug(e.getMessage());
		}
		return result;
	}

	public static String getValueOfElement(String xml, String xPath) {
		if (StringUtil.isEmptyString(xml) || StringUtil.isEmptyString(xPath)) {
			return "";
		}
		try {
			SAXReader reader = new SAXReader();
			Document document = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			Element element = (Element) document.selectSingleNode(xPath);
			if (element != null) {
				return element.getText();
			}
		} catch (UnsupportedEncodingException e) {
			log.debug(e.getMessage());
		} catch (DocumentException e) {
			log.debug(e.getMessage());
		}
		return "";
	}

	public static String setValueOfElement(String xml, String xPath, String value) {
		if (StringUtil.isEmptyString(xml) || StringUtil.isEmptyString(xPath)) {
			return "";
		}
		try {
			SAXReader reader = new SAXReader();
			Document document = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			Element element = (Element) document.selectSingleNode(xPath);
			if (element != null) {
				element.setText(value);
			}
			return document.asXML();
		} catch (UnsupportedEncodingException e) {
			log.debug(e.getMessage());
		} catch (DocumentException e) {
			log.debug(e.getMessage());
		}
		return "";
	}

	public static String setChilrenOfElement(String xml, String parentXPath, String childTag, List<String> value) {
		if (StringUtil.isEmptyString(xml)
				|| StringUtil.isEmptyString(parentXPath)
				|| StringUtil.isEmptyString(childTag) || value == null) {
			return "";
		}
		try {
			SAXReader reader = new SAXReader();
			Document document = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			Element parentElement = (Element) document.selectSingleNode(parentXPath);
			if (parentElement != null) {
				parentElement.clearContent();
				for (String childValue : value) {
					Element childElement = parentElement.addElement(childTag);
					childElement.setText(childValue);
				}
			}
			return document.asXML();
		} catch (UnsupportedEncodingException e) {
			log.debug(e.getMessage());
		} catch (DocumentException e) {
			log.debug(e.getMessage());
		}
		return "";
	}

	/**
	 * Convert dom4j document to w3c element
	 * @throws DocumentException
	 **/
	public static org.w3c.dom.Element convertDom4jDoc2W3cElement(
			org.dom4j.Document document) throws DocumentException {
		DOMWriter writer = new DOMWriter();
		org.w3c.dom.Document doc2 = writer.write(document);
		return doc2.getDocumentElement();
	}

	/**
	 * Convert w3c element to dom4j element
	 * @throws ParserConfigurationException
	 **/
	public static org.dom4j.Document convertW3cElement2Dom4jDocument(
			org.w3c.dom.Document document) throws ParserConfigurationException {
		DOMReader reader = new DOMReader();
		org.dom4j.Document doc2 = reader.read(document);
		return doc2;
	}

	public static String replaceXMLSpecialChar(String targetString) {
		targetString.replace("<", "&lt;");
		targetString.replace("&", "&amp;");
		targetString.replace(">", "&gt;");
		targetString.replace("\"", "&quot;");
		targetString.replace("'", "&apos;");
		return targetString;
	}

	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10; i++) {
			long start = System.currentTimeMillis();
			Element elem = validateXMLByXSD("c:/xml/AIM_new.xml", "c:/xml/AIM_1p0.xsd");
			long end = System.currentTimeMillis();
			log.debug(elem);
			log.debug("time spent in validation:" + (end - start));
		}
	}
}
